﻿using System;
using System.Collections.Generic;
using NAnt.Core;
using NAnt.Core.Attributes;
using System.IO;
using NAnt.SVN.Util;
using NAnt.SVN.Tasks;
using NAnt.SVN.UtilTasks.Constants;

namespace NAnt.SVN.UtilTasks
{
    /// <summary>
    /// Use this class to get the buildnumber of a project. This tasks
    /// scans through the files looking for AssemblyInfo.
    /// Run the getbuildnumber task in NAnt.
    /// $Id$
    /// </summary>
    [TaskName("getbuildnumber")]
    public sealed class GetBuildNumber : BaseExtensionTask
    {
        #region Private Fields

        //Internal task flags
        bool recursive = true;
        int currentrevision = -1;
        string[] assemblyinfofiles;
        bool assemblyinfofilesfound = false;
        bool assemblyinfofileparsed = false;

        //Retrieved flags
        int major = 0;
        int minor = 0;
        int build = 0;
        int revision = 0;
        string title = Resources.Strings.UTIL0103;
        string description = Resources.Strings.UTIL0104;
        string fileversion = "";

        #endregion

        #region Public Fields

        public static string ASSEMBLYINFO = "AssemblyInfo.cs";

        /// <summary>
        /// Check the files recursively
        /// </summary>
        [TaskAttribute("recursive",Required=false)]
        public bool Recursive
        {
            get { return recursive; }
            set { recursive = value; }
        }

        /// <summary>
        /// The current revision
        /// </summary>
        [TaskAttribute("revision", Required = false)]
        [Int32Validator()]
        public int CurrentRevision
        {
            get { return currentrevision; }
            set { currentrevision = value; }
        }

        /// <summary>
        /// Check if a revision is set. If no current
        /// revision is set, the current revision equals
        /// -1
        /// </summary>
        public bool IsCurrentRevision
        {
            get { return currentrevision != -1; }
        }

        /// <summary>
        /// Major program number
        /// </summary>
        public int Major
        {
            get { return major; }
        }

        /// <summary>
        /// Minor program number
        /// </summary>
        public int Minor
        {
            get { return minor; }
        }

        /// <summary>
        /// Build number
        /// </summary>
        public int Build
        {
            get
            {
                return build;
            }
        }

        /// <summary>
        /// Revision number
        /// </summary>
        public int Revision
        {
            get
            {
                return revision;
            }
        }

        /// <summary>
        /// Get major, minor, build and revision as an array
        /// </summary>
        public int[] FullBuildNumberAsArray
        {
            get
            {
                int[] bin = new int[4];
                bin[0] = major;
                bin[1] = minor;
                bin[2] = build;
                bin[3] = revision;
                return bin;
            }
        }

        /// <summary>
        /// Get the full build as a 0.0.0.0 like
        /// string
        /// </summary>
        public string FullBuildNumber
        {
            get
            {
                return string.Join(".", FullBuildNumberAsArray);
            }
        }

        /// <summary>
        /// Get the full file version of the assembly as a 0.0.0.0
        /// number
        /// </summary>
        public string FileVersionNumber
        {
            get
            {
                return fileversion;
            }
        }

        #endregion

        /// <summary>
        /// Main exec function
        /// </summary>
        protected override void ExecuteTask()
        {
            //Search the assembly info files
            assemblyinfofilesfound = searchAssemblyInfoFiles();
            //Get current revision number
            if (!IsCurrentRevision)
            {
                if (PropertiesManager.IsSVNProperty(Resources.PropertyNames.Revision))
                    revision = int.Parse(PropertiesManager.GetSVNProperty(Resources.PropertyNames.Revision));
                else
                {
                    Info infoTask = new Info();
                    this.CopyTo(infoTask);
                    infoTask.Execute();
                    revision = int.Parse(PropertiesManager.GetSVNProperty(Resources.PropertyNames.Revision));
                }
            }
            else
                revision = CurrentRevision;
            //Parse and write the files
            if (assemblyinfofilesfound)
            {
                foreach (string file in assemblyinfofiles)
                {
                    parseAssemblyInfofile(file);
                }
                //Set all properties
                PropertiesManager.SetSVNProperty(Resources.PropertyNames.ProjectTitle, title);
                PropertiesManager.SetSVNProperty(Resources.PropertyNames.ProjectDescription, description);
                PropertiesManager.SetSVNProperty(Resources.PropertyNames.Major, major);
                PropertiesManager.SetSVNProperty(Resources.PropertyNames.Minor, minor);
                PropertiesManager.SetSVNProperty(Resources.PropertyNames.Build, build);
                PropertiesManager.SetSVNProperty(Resources.PropertyNames.FileVersion, fileversion);
                PropertiesManager.SetSVNProperty(Resources.PropertyNames.FullBuildNumber, FullBuildNumber);
                //Log some things
                Log(Level.Info, string.Format(Resources.Strings.SVN0105, Resources.PropertyNames.FullBuildNumber, FullBuildNumber));
                Log(Level.Info, string.Format(Resources.Strings.SVN0105, Resources.PropertyNames.FileVersion, fileversion));
            }
            else
            {
                Log(Level.Warning, Resources.Strings.UTIL0307);
            }
        }

        #region Support Functions

        /// <summary>
        /// Search the assembly info files
        /// </summary>
        private bool searchAssemblyInfoFiles()
        {
            //Search files
            SearchOption so = (Recursive) ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly;
            assemblyinfofiles = Directory.GetFiles(Project.BaseDirectory, ASSEMBLYINFO, so);
            //Log it
            Log(Level.Verbose, string.Format(Resources.Strings.UTIL0201, assemblyinfofiles.Length));
            foreach (string f in assemblyinfofiles) { Log(Level.Verbose, string.Format(Resources.Strings.UTIL0202, f)); }
            //Set flag
            return (assemblyinfofiles.Length > 0);
        }

        /// <summary>
        /// Parse the assembly file info
        /// </summary>
        /// <param name="file"></param>
        private void parseAssemblyInfofile(string file)
        {
            if (File.Exists(file))
            {
                Log(Level.Verbose, string.Format(Resources.Strings.UTIL0203, file));
                //Read the file and store it in a list
                List<string> filebuffer = new List<string>();
                string line;
                StreamReader read = new StreamReader(file);
                while ((line = read.ReadLine()) != null)
                {
                    if (line.Contains(AssemblyInfoParts.ASSEMBLYTITLE) && !isCommentline(line)) parseAssemblyTitle(line);
                    else if (line.Contains(AssemblyInfoParts.ASSEMBLYDESCRIPTION) && !isCommentline(line)) parseAssemblyDescription(line);
                    else if (line.Contains(AssemblyInfoParts.ASSEMBLYVERSION) && !isCommentline(line))
                    {
                        parseAssemblyVersion(line);
                    }
                    else if (line.Contains(AssemblyInfoParts.ASSEMBLYFILEVERSION) && !isCommentline(line))
                    {
                        DateTime now = DateTime.Now;
                        fileversion = now.Day + "." + now.Month + "." + now.Year + "." + revision;
                    }
                    filebuffer.Add(line);
                }
                read.Close();
            }
            else
                Log(Level.Error, string.Format(Resources.Strings.SVNFILENOTFOUND, file));
        }

        /// <summary>
        /// Parse the assembly version
        /// </summary>
        /// <param name="line"></param>
        private void parseAssemblyVersion(string line)
        {
            if (!assemblyinfofileparsed)
            {
                //Remove attribute and replace * by 0.0
                line = stripAttributeFromLine(line, AssemblyInfoParts.ASSEMBLYVERSION).Replace("*", "0.0");
                string[] parts = line.Split('.');
                if (parts.Length == 4)
                {
                    //Assign values
                    major = int.Parse(parts[0]);
                    minor = int.Parse(parts[1]);
                    build = int.Parse(parts[2]) + 1;
                    //Set flag
                    assemblyinfofileparsed = true;
                }
                else Log(Level.Error, string.Format(Resources.Strings.UTIL0308, line));
            }
        }

        /// <summary>
        /// Parse assemblytitle
        /// </summary>
        /// <param name="line"></param>
        private void parseAssemblyTitle(string line)
        {
            if (title.Equals(Resources.Strings.UTIL0103))
            {
                line = stripAttributeFromLine(line, AssemblyInfoParts.ASSEMBLYTITLE);
                title = line;
            }
        }

        /// <summary>
        /// Parse assemblydescription
        /// </summary>
        /// <param name="line"></param>
        private void parseAssemblyDescription(string line)
        {
            if (description.Equals(Resources.Strings.UTIL0104))
            {
                line = stripAttributeFromLine(line, AssemblyInfoParts.ASSEMBLYDESCRIPTION);
                description = line;
            }
        }

        /// <summary>
        /// Strip the attribute
        /// </summary>
        /// <param name="line"></param>
        /// <param name="attribute"></param>
        /// <returns></returns>
        private string stripAttributeFromLine(string line, string attribute)
        {
            return line.Replace("[assembly: " + attribute + "(\"", "").Replace("\")]", "");
        }

        /// <summary>
        /// Check if a line is a comment
        /// </summary>
        /// <param name="line"></param>
        /// <returns></returns>
        private bool isCommentline(string line)
        {
            if (line.Length < 2) return false;
            else return line.Substring(0, 2).Equals("//");
        }

        #endregion
    }
}
